#ifndef LIGHTING_GLSL
#define LIGHTING_GLSL


void FetchLightProperties(
    in Light light,
#if !defined(SHADOW_RAY_ONLY)    
    in vec3 Wo,
#endif    
    in vec3 P,
    in vec3 N,
#if defined(SOFT_SHADOWS)
    in vec2 rng,
#endif 
#if !defined(SHADOW_RAY_ONLY)
    out vec3 Li,
#endif    
    out vec3 Wi,
#if !defined(SHADOW_RAY_ONLY)
    out vec3 Wh,
#endif 
#if defined(RAY_TRACING)
    out float tMax,
#endif    
    out float attenuation
)
{
    const int type = GetLightType(light);

#if !defined(SHADOW_RAY_ONLY)
    Li = GetLightColor(light) * GetLightIntensity(light);  
#endif

    if (type == LIGHT_TYPE_DIRECTIONAL)
    {
        vec3 lightDir       = GetLightDirection(light);

#if defined(SOFT_SHADOWS)
        vec3 lightTangent   = normalize(cross(lightDir, vec3(0.0f, 1.0f, 0.0f)));
        vec3 lightBitangent = normalize(cross(lightTangent, lightDir));

        // calculate disk point
        float pointRadius = GetLightRadius(light) * sqrt(rng.x);
        float pointAngle  = rng.y * 2.0f * M_PI;
        vec2  diskPoint   = vec2(pointRadius * cos(pointAngle), pointRadius * sin(pointAngle));    
        Wi = normalize(lightDir + diskPoint.x * lightTangent + diskPoint.y * lightBitangent);
#else
        Wi = lightDir;
#endif
#if defined(RAY_TRACING)
        tMax = 10000.0f;
#endif  
        attenuation = 1.0f;
    }
    else if (type == LIGHT_TYPE_POINT)
    {        
        vec3  toLight       = GetLightPosition(light) - P;
        vec3  lightDir       = normalize(toLight);
        float lightDistance = length(toLight);

#if defined(SOFT_SHADOWS)
        vec3 lightTangent   = normalize(cross(lightDir, vec3(0.0f, 1.0f, 0.0f)));
        vec3 lightBitangent = normalize(cross(lightTangent, lightDir));  
        
        // calculate disk point
        float currentLightRadius = GetLightRadius(light) / lightDistance;  
        float pointRadius = currentLightRadius * sqrt(rng.x);
        float pointAngle  = rng.y * 2.0f * M_PI;
        vec2  diskPoint   = vec2(pointRadius * cos(pointAngle), pointRadius * sin(pointAngle));    
        Wi = normalize(lightDir + diskPoint.x * lightTangent + diskPoint.y * lightBitangent);
#else
        Wi = lightDir;
#endif
#if defined(RAY_TRACING)
        tMax = lightDistance;
#endif  
        attenuation = (1.0f / (lightDistance * lightDistance));
    }
    else
    {
        vec3  toLight       = GetLightPosition(light) - P;
        vec3  lightDir      = normalize(toLight);
        float lightDistance = length(toLight);

#if defined(SOFT_SHADOWS)    
        vec3 lightTangent   = normalize(cross(lightDir, vec3(0.0f, 1.0f, 0.0f)));
        vec3 lightBitangent = normalize(cross(lightTangent, lightDir));  
        
        // calculate disk point
        float currentLightRadius = GetLightRadius(light) / lightDistance;  
        float pointRadius = currentLightRadius * sqrt(rng.x);
        float pointAngle  = rng.y * 2.0f * M_PI;
        vec2  diskPoint   = vec2(pointRadius * cos(pointAngle), pointRadius * sin(pointAngle));    
        Wi = normalize(lightDir + diskPoint.x * lightTangent + diskPoint.y * lightBitangent); 
#else
        Wi = lightDir;
#endif       
#if defined(RAY_TRACING)
        tMax = lightDistance;
#endif  
        float angleAttenuation = dot(Wi, GetLightDirection(light));
        angleAttenuation       = smoothstep(GetLightCosThetaOuter(light), GetLightCosThetaInner(light), angleAttenuation);    
        attenuation = (angleAttenuation / (lightDistance * lightDistance));    
    }

#if !defined(SHADOW_RAY_ONLY)
    Wh  = normalize(Wo + Wi); 
#endif     
    attenuation *= clamp(dot(N, Wi), 0.0, 1.0);
}

// ------------------------------------------------------------------------

#if !defined(SHADOW_RAY_ONLY)

vec3 DirectLighting(
    in Light light,   
    in vec3 Wo, 
    in vec3 N, 
    in vec3 P, 
    in vec3 F0, 
    in vec3 diffuseColor, 
    in float roughness
#if defined(RAY_THROUGHPUT)
  , in vec3 T
#endif    
#if defined(SOFT_SHADOWS)
  , in vec2 rng1
#endif
#if defined(SAMPLE_SKY_LIGHT)
  , in vec2 rng2
  , in samplerCube skyCubemap
#endif
)
{
    vec3 Lo = vec3(0.0f);

#if !defined(RAY_THROUGHPUT)
    vec3 T = vec3(1.0f);
#endif

    vec3 rayOrigin = P + N * 0.1f;

    // Punctual Light
    {
        vec3 Li;
        vec3 Wi;
        vec3 Wh;
    #if defined(RAY_TRACING)    
        float tMax;
    #endif    
        float attenuation; 

        FetchLightProperties(light, 
                               Wo, 
                               P, 
                               N, 
                            #if defined(SOFT_SHADOWS)
                               rng1,
                            #endif 
                               Li, 
                               Wi, 
                               Wh, 
                            #if defined(RAY_TRACING)
                               tMax, 
                            #endif
                               attenuation);

#if defined(RAY_TRACING)
        if (attenuation > 0.0f)
            attenuation *= QueryDistance(rayOrigin, Wi, tMax);
#endif 

        vec3  brdf = EvaluateUberBrdf(diffuseColor, roughness, N, F0, Wo, Wh, Wi);
        Lo += T * brdf * attenuation * Li;  
    }

#if defined(SAMPLE_SKY_LIGHT)
    // Sky Light
    {
        vec3  Wi  = SampleCosineLobe(N, rng2);
        vec3  Li  = texture(skyCubemap, Wi).rgb;
        float pdf = PdfCosineLobe(dot(N, Wi));
        vec3  Wh  = normalize(Wo + Wi);

        // fire shadow ray for visiblity
        Li *= QueryDistance(rayOrigin, Wi, 10000.0f);

        vec3  brdf = EvaluateUberBrdf(diffuseColor, roughness, N, F0, Wo, Wh, Wi);
        Lo += T * brdf * Li;  
    }
#endif    

    return Lo;
}

#endif

#endif